package com.giscloud.amp.controller.login;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.giscloud.amp.controller.BaseController;
import com.giscloud.amp.entity.AmpLoginLog;
import com.giscloud.amp.entity.dto.AmpStaffAuthRelDTO;
import com.giscloud.amp.entity.dto.AmpStaffDTO;
import com.giscloud.amp.entity.dto.AmpStaffOrgDTO;
import com.giscloud.amp.entity.dto.PasswordDTO;
import com.giscloud.amp.entity.login.LoginDTO;
import com.giscloud.amp.entity.vo.AmpMenuVO;
import com.giscloud.amp.entity.vo.AmpStaffSystemOrgVO;
import com.giscloud.amp.entity.vo.AmpStaffVO;
import com.giscloud.amp.service.auth.AmpAuthService;
import com.giscloud.amp.service.auth.AmpStaffDataService;
import com.giscloud.amp.service.auth.AmpStaffService;
import com.giscloud.amp.service.sys.LoginLogService;
import com.giscloud.captcha.base.Captcha;
import com.giscloud.captcha.core.ArithmeticCaptcha;
import com.giscloud.captcha.core.ChineseCaptcha;
import com.giscloud.captcha.core.GifCaptcha;
import com.giscloud.captcha.core.SpecCaptcha;
import com.giscloud.commons.codec.CipherUtils;
import com.giscloud.commons.codec.DigestsUtils;
import com.giscloud.commons.model.Jwt;
import com.giscloud.commons.model.Result;
import com.giscloud.commons.utils.*;
import com.giscloud.redis.utils.RedisUtils;
import com.giscloud.token.utils.RedisConstant;
import com.giscloud.token.utils.Token;
import com.giscloud.token.utils.TokenUserUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Controller;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 登录模块
 */
@Controller
@RequestMapping("/amp")
@Log4j2
@ConditionalOnExpression("'${spring.cache.type}'=='redis'")
public class RedisLoginController extends BaseController {
    /**
     * 名秘钥,可以换成 秘钥 注入
     */
    @Value("${giscloud.jwt.secret: eed3c844dc9247aa9959581081f7495e}")
    private String secret;
    /**
     * 签发地
     */
    @Value("${giscloud.jwt.issuer: giscloud.top}")
    private String issuer;
    /**
     * 过期时间 7天
     */
    @Value("${giscloud.jwt.ttlMillis: 604800000}")
    private Long ttlMillis;
    /**
     * 验证码key
     */
    @Value("${giscloud.valid.key:valid_key}")
    private String validateCodeKey;
    /**
     * 登录失败次数
     */
    @Value("${giscloud.login.count: 5}")
    private Long loginCount;

    @Value("${giscloud.login.day: 30}")
    private int day;
    @Resource
    private AmpStaffService ampStaffService;
    @Resource
    private LoginLogService loginLogService;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private AmpStaffDataService ampStaffDataService;
    @Autowired
    private TokenUserUtil tokenUserUtil;
    @Autowired
    private AmpAuthService ampAuthService;
    /**
     * 登录
     * @param loginDTO
     * @param request
     * @return
     */
    @PostMapping(value = "/login")
    @ResponseBody
    public Result<Token> doLogin(@Valid @RequestBody  LoginDTO loginDTO,
                                 HttpServletRequest request){
        String remoteIp = NetUtils.getRemoteIp(request);
        log.debug("the remote ip is " + remoteIp);
        String inputValidCode = loginDTO.getCode();
        String validateCode = CookieUtils.getCookieValue(validateCodeKey, request);
        Object cacheValidCode = redisUtils.get(validateCode);
        if (ObjectUtils.isEmpty(cacheValidCode)) {
            log.error("验证码已过期...");
            return Result.error(4,"验证码已过期,请刷新验证码");
        }
        if (!inputValidCode.equalsIgnoreCase(cacheValidCode.toString())) {
            log.error("验证码不匹配...");
            return Result.error(5,"验证码不匹配");
        }
        AmpStaffDTO ampStaffDTO = new AmpStaffDTO();
        ampStaffDTO.setLoginName(loginDTO.getLoginName());
        AmpStaffVO ampStaffVO = ampStaffService.getAmpStaffByCondition(ampStaffDTO);
        try{
            if (ampStaffVO == null || ampStaffVO.getStatusCd() == 2) {
                log.error("# 账号不存在");
                return Result.error(6,"账号或者密码错误");
            }
            if (ampStaffVO.getStatusCd() == 3) {
                log.error("# 账号被禁用");
                return Result.error(6,"账号或者密码错误");

            }
            if (!PasswordUtils.verify(loginDTO.getPassword(), ampStaffVO.getPassword())) {
                return Result.error(6,"账号或者密码错误");
            }
            log.debug("#账号密码验证通过.....................................");
            Jwt jwt = new Jwt();
            jwt.setSecret(secret);
            jwt.setIssuer(issuer);
            jwt.setTtlMillis(ttlMillis);
            String accessToken = TokenUtil.createJwtToken(String.valueOf(ampStaffVO.getStaffId()),jwt);
            //记录登录日志到数据库
            String ip = IPAddressUtils.getIpAddress(request);
            AmpLoginLog ampLoginLog = new AmpLoginLog();
            ampLoginLog.setLoginDate(new Date());
            ampLoginLog.setLoginStaffId(ampStaffVO.getStaffId());
            ampLoginLog.setRemark("登录");
            ampLoginLog.setOnlineStatusCd((short) 1);
            ampLoginLog.setToken(accessToken);
            ampLoginLog.setIp(ip);
            ampLoginLog.setSystemId(loginDTO.getSystemId());
            loginLogService.save(ampLoginLog);
            AmpStaffOrgDTO ampStaffOrgDTO = new AmpStaffOrgDTO();
            ampStaffOrgDTO.setStaffId(ampStaffVO.getStaffId());
            List<AmpStaffSystemOrgVO> ampStaffSystemOrgVOs = ampStaffDataService.getOrgByStaffId(ampStaffOrgDTO);
            Map orgMap = Maps.newHashMapWithExpectedSize(2);
            for (AmpStaffSystemOrgVO ampStaffSystemOrgVO : ampStaffSystemOrgVOs) {
                Map<String, String> map = convertToMap(ampStaffSystemOrgVO);
                map.remove("staffId");
                map.remove("staffName");
                orgMap.put(ampStaffSystemOrgVO.getSystemId().toString(),map);
            }

            AmpStaffAuthRelDTO ampStaffAuthRelDTO = new AmpStaffAuthRelDTO();
            ampStaffAuthRelDTO.setStaffId(ampStaffVO.getStaffId());
            ampStaffAuthRelDTO.setRoles(ampStaffVO.getRolesId());
            Token token = new Token();
            ampStaffAuthRelDTO.setSystemId(loginDTO.getSystemId());
            ampStaffAuthRelDTO.setAuthTypes(Arrays.asList(new String[]{"button"}));
            List<AmpMenuVO> allAmpAuth = ampAuthService.getAmpAuth(ampStaffAuthRelDTO);
            List<String> authCodes = Lists.newArrayListWithCapacity(4);
            if(CollectionUtil.isNotEmpty(allAmpAuth)){
                for (AmpMenuVO ampMenuVO : allAmpAuth) {
                    authCodes.add(ampMenuVO.getAuthCode());
                }
            }
            token.setAuthCodes(authCodes);
            token.setStaffId(ampStaffVO.getStaffId());
            token.setAccessToken(accessToken);
            token.setRolesId(ampStaffVO.getRolesId());
            token.setLoginName(ampStaffVO.getLoginName());
            token.setStaffName(ampStaffVO.getStaffName());
            token.setIp(ip);
            token.setOrgMap(orgMap);
            token.setSystemId(loginDTO.getSystemId());
            token.setLoginTime(DateUtil.now()+"|"+DateUtil.now());
            //登录成功,将Token作为键，用户信息作为值存入Redis
            String date = DatePattern.PURE_DATE_FORMAT.format(new Date());
            redisUtils.setBitmap(RedisConstant.REDIS_ON_LINE_SYSTEM+loginDTO.getSystemId()+":"+date,token.getStaffId(),true,day);
            redisUtils.set(RedisConstant.REDIS_ON_LINE_SYSTEM_STAFF+loginDTO.getSystemId()+":"+ token.getStaffId() , JacksonUtil.bean2Json(token), RedisConstant.REDIS_EXPIRE_TIME_ONE_DAY);
            redisUtils.set(RedisConstant.REDIS_USER_TOKEN + accessToken , JacksonUtil.bean2Json(token), RedisConstant.REDIS_EXPIRE_TIME_ONE_DAY);
            return Result.success(token,"登录成功");
        }catch (Exception e){
            log.error("登录异常:{}",e);
            return Result.error(7,"登录异常");
        }
    }
    public static Map<String, String> convertToMap(Object obj) {
        try {
            if (obj instanceof Map) {
                return (Map)obj;
            }
            Map<String, String> returnMap = BeanUtils.describe(obj);
            returnMap.remove("class");
            return returnMap;
        } catch (IllegalAccessException e1) {
            e1.getMessage();
        } catch (InvocationTargetException e2) {
            e2.getMessage();
        } catch (NoSuchMethodException e3) {
            e3.getMessage();
        }
        return new HashMap();
    }

    /**
     * 退出
     * @return
     */
    @SneakyThrows
    @RequestMapping(value = "/logout")
    @ResponseBody
    public Result logout() {
        Object obj = redisUtils.get(RedisConstant.REDIS_USER_TOKEN + getAccessToken());
        Token token = null;
        if(obj != null){
            token = JacksonUtil.json2Bean(obj.toString(),Token.class);
            String date = DatePattern.PURE_DATE_FORMAT.format(new Date());
            redisUtils.setBitmap(RedisConstant.REDIS_ON_LINE_SYSTEM+token.getSystemId()+":"+date,token.getStaffId(),false,day);
            redisUtils.del(RedisConstant.REDIS_ON_LINE_SYSTEM_STAFF +token.getSystemId()+":"+ token.getStaffId());
            //记录登录日志到数据库
            String ip = IPAddressUtils.getIpAddress(getRequest());
            AmpLoginLog ampLoginLog = new AmpLoginLog();
            ampLoginLog.setLoginDate(new Date());
            ampLoginLog.setLoginStaffId(token.getStaffId());
            ampLoginLog.setRemark("退出");
            ampLoginLog.setOnlineStatusCd((short) 0);
            ampLoginLog.setToken(token.getAccessToken());
            ampLoginLog.setIp(ip);
            ampLoginLog.setSystemId(token.getSystemId());
            loginLogService.save(ampLoginLog);
        }
        redisUtils.del(RedisConstant.REDIS_USER_TOKEN + getAccessToken());
        return ResultUtil.success("退出成功");
    }

    /**
     * 获取验证码
     * @param resp
     * @throws Exception
     */
    @RequestMapping("/validateCode")
    public void createValidateCode(HttpServletResponse resp) throws Exception {
        // 获取验证码信息
        ImageCaptcha vCode = new ImageCaptcha(90, 30, 4, 150);

        // 生成redis获取验证码的KEY保存到cookie中
        String validateCode = CipherUtils.MD5Encode(UUID.randomUUID().toString());

        CookieUtils.addCookie(resp, validateCodeKey, validateCode, null, "/");
        // 保存验证码到缓存，时间为1分钟
        redisUtils.set(validateCode,vCode.getCode(),1L,TimeUnit.MINUTES);
        // 禁止图像缓存。
        resp.setHeader("Pragma", "no-cache");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setDateHeader("Expires", 0);
        resp.setContentType("image/jpeg");
        ServletOutputStream sos = null;
        try {
            sos = resp.getOutputStream();
            ImageIO.write(vCode.getBuffImg(), "jpeg", sos);
            sos.flush();
        }catch (Exception e){
            log.error("生成验证码异常：{}",e);
        }finally {
            sos.close();
        }
    }

    /**
     * png类型，存到到redis
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @ResponseBody
    @RequestMapping("/captchas")
    public Result<Map> captchas(HttpServletRequest request, HttpServletResponse response) throws Exception {
        Map map = Maps.newHashMap();
        SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
        String verCode = specCaptcha.text().toLowerCase();
        String key = UUID.randomUUID().toString();
        // 存入redis并设置过期时间为30分钟
        redisUtils.setEx(key, verCode, 30, TimeUnit.MINUTES);
        map.put("key", key);
        map.put("image", specCaptcha.toBase64());
        // 将key和base64返回给前端
        return Result.success(map);
    }
    /**
     * png类型
     * @param request
     * @param response
     * @throws Exception
     */
    @RequestMapping("/captcha")
    public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        // 三个参数分别为宽、高、位数
        SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
        // 设置字体
        specCaptcha.setFont(new Font("Verdana", Font.PLAIN, 32));  // 有默认字体，可以不用设置
        // 设置类型，纯数字、纯字母、字母数字混合
        specCaptcha.setCharType(Captcha.TYPE_NUM_AND_UPPER);

        // 验证码存入session
        request.getSession().setAttribute("captcha", specCaptcha.text().toLowerCase());

        // 输出图片流
        specCaptcha.out(response.getOutputStream());
    }

    /**
     * 算术类型
     * @param request
     * @param response
     * @throws Exception
     */
    @RequestMapping("/arithmeticCaptcha")
    public void arithmeticCaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        // 三个参数分别为宽、高、位数
        ArithmeticCaptcha specCaptcha = new ArithmeticCaptcha(180, 48, 5);
        // 设置字体
        specCaptcha.setFont(new Font("Verdana", Font.PLAIN, 32));  // 有默认字体，可以不用设置
        // 设置类型，纯数字、纯字母、字母数字混合
        specCaptcha.setCharType(Captcha.TYPE_ONLY_NUMBER);

        // 验证码存入session
        request.getSession().setAttribute("captcha", specCaptcha.text().toLowerCase());

        // 输出图片流
        specCaptcha.out(response.getOutputStream());
    }

    /**
     * gif类型
     * @param request
     * @param response
     * @throws Exception
     */
    @RequestMapping("/gifCaptcha")
    public void gifCaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        // 三个参数分别为宽、高、位数
        GifCaptcha specCaptcha = new GifCaptcha(130, 48, 5);
        // 设置字体
        specCaptcha.setFont(new Font("Verdana", Font.PLAIN, 32));  // 有默认字体，可以不用设置
        // 设置类型，纯数字、纯字母、字母数字混合
        specCaptcha.setCharType(Captcha.TYPE_ONLY_NUMBER);

        // 验证码存入session
        request.getSession().setAttribute("captcha", specCaptcha.text().toLowerCase());

        // 输出图片流
        specCaptcha.out(response.getOutputStream());
    }

    /**
     * 中文类型
     * @param request
     * @param response
     * @throws Exception
     */
    @RequestMapping("/chineseGifCaptcha")
    public void chineseGifCaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        // 三个参数分别为宽、高、位数
        ChineseCaptcha captcha = new ChineseCaptcha(130, 48);        // 设置字体

        // 验证码存入session
        request.getSession().setAttribute("captcha", captcha.text().toLowerCase());

        // 输出图片流
        captcha.out(response.getOutputStream());
    }
    public static void main(String[] args) {
        String password = "123456";
        String md5Pass = DigestsUtils.MD5Encode(DigestsUtils.MD5Encode(password.toUpperCase()).toUpperCase()).toLowerCase();
        System.out.println(md5Pass);
    }

    /**
     * 修改密码
     * @param passwordDTO
     * @return
     * @throws Exception
     */
    @RequestMapping("/user/changePassword")
    @ResponseBody
    public Result changePassword(@RequestBody PasswordDTO passwordDTO){
        passwordDTO.setStaffId(tokenUserUtil.getUserId());
        return ampStaffService.changePassword(passwordDTO);
    }
}
